import Color from "@/utils/Color"
import Arc from "./Arc"
import Text from "./Text"
import Line from "./Line"
import { v4 as uuidv4 } from 'uuid'
import {fillRect, strokeRect, fillText, drawArrow, moveTo, lineTo, clearRect} from "@/utils/common"
import Endpoint from "./endpoint"
import State from "@/utils/State"

export class Wrapper extends Text {
  constructor(x, y, arcX, arcY, value, key, index, color) {
    super(x, y, value, key)
    this.arc = new Arc(arcX, arcY, index, color)
  }

  draw(ctx) {
    this.arc.draw(ctx)
    super.draw(ctx)
  }

  deepCopy() {
    let wrap = new Wrapper(this.x, this.y, this.arc.x, this.arc.y, this.value, this.key, this.arc.index, this.arc.color)
    let arc = wrap.arc
    arc.setLeft(this.arc.left)
    arc.setRight(this.arc.right)
    return wrap
  }

  static createWrappers(nums, ctx) {

    let wrappers = []
    const startX = document.getElementById('canvas-container').clientWidth / 2;
    const startY = 100;
    const dis = [250, 120, 70, 30, 10];

    let wrap = new Wrapper(startX, startY, startX, startY, nums[0], 0, 0, Color.GREEN_1);
    wrappers.push(wrap);
    for (let i = 1; i < nums.length; i++) {
      // 父亲的坐标
      let parIndex = Math.floor((i - 1) / 2);
      // 父亲所在的层
      let layer = Math.floor(Math.log(i + 1) / Math.log(2));
      let x = wrappers[parIndex].x;
      let y = wrappers[parIndex].y;
      let x1 = x;
      let y1 = y;
      if (parIndex * 2 + 2 === i) {
        x1 = x + dis[layer - 1];
      } else {
        // 左孩子
        x1 = x - dis[layer - 1];
      }
      y1 = y + 70;
      let k = (y - y1) / (x - x1);

      let rad = Math.atan(k);
      rad = rad < 0 ? rad + Math.PI : rad
      let fromX = x + Arc.R * Math.cos(rad);
      let fromY = y + Arc.R * Math.sin(rad);
      let toX = x1 + Arc.R * Math.cos(rad) * -1;
      let toY = y1 + Arc.R * Math.sin(rad) * -1;
      let line = new Line(fromX, fromY, toX, toY);
      if (parIndex * 2 + 2 === i) {
        wrappers[parIndex].arc.setRight(line);
      } else {
        wrappers[parIndex].arc.setLeft(line);
      }

      wrap = new Wrapper(x1, y1, x1, y1, nums[i], i, i, Color.GREEN_1);
      wrappers.push(wrap);
    }

    for (let i = 0; i < wrappers.length; i++) {
      wrappers[i].draw(ctx)
    }

    return wrappers;

  }
}

export class Wrapper2 extends Text {
  constructor(x, y, rx, ry, value, key, index, rColor) {
    super(x, y, value, key)
    this.rx = rx
    this.ry = ry
    this.rColor = rColor
    this.index = index
  }

  draw(ctx) {
    ctx.beginPath()
    ctx.fillStyle = this.rColor
    fillRect(ctx, this.rx, this.ry, Wrapper2.width, Wrapper2.height)
    ctx.fillStyle = 'black'
    strokeRect(ctx, this.rx, this.ry, Wrapper2.width, Wrapper2.height)
    ctx.fillStyle = 'red'
    fillText(ctx, this.index, this.rx + Wrapper2.width / 2, this.ry + Wrapper2.height + 10)
    ctx.fillStyle = 'black'
    // 绘制内容
    super.draw(ctx)
  }

  deepCopy() {
    const { x, y, rx, ry, value, key, index, rColor } = this
    return new Wrapper2(x, y, rx, ry, value, key, index, rColor)
  }

  static createWrappers(nums, ctx) {
    let wrappers = []
    const startX = document.getElementById('canvas-container').clientWidth / 2 - nums.length * Wrapper2.width / 2
    const startY = document.getElementById('canvas-container').clientHeight - 100
    for (let i = 0; i < nums.length; i++) {
      let rx = startX + Wrapper2.width * i - (i > 0 ? 1 : 0)
      let ry = startY

      let x = rx + Wrapper2.width / 2
      let y = ry + Wrapper2.height / 2
      let wrap = new Wrapper2(x, y, rx, ry, nums[i], uuidv4(), i, 'white')
      wrappers.push(wrap)
    }

    for (let i = 0; i < wrappers.length; i++) {
      wrappers[i].draw(ctx)
    }

    return wrappers
  }

  static width = 40
  static height = 30
}

export class SequentialStack extends Text {
  constructor(x, y, rx, ry, value, key, index, rColor) {
    super(x, y, value, key);
    this.rx = rx;    // 方框左上角x坐标
    this.ry = ry;    // 方框左上角y坐标
    this.rColor = rColor; // 方框颜色
    this.index = index;   // 下标
  }

  draw(ctx) {
    // 绘制方框
    ctx.beginPath();
    ctx.fillStyle = this.rColor;
    fillRect(ctx, this.rx, this.ry, SequentialStack.width, SequentialStack.height);
    ctx.fillStyle = "black";
    strokeRect(ctx, this.rx, this.ry, SequentialStack.width, SequentialStack.height);

    // 在方框中心绘制值
    ctx.fillStyle = "black";
    super.draw(ctx); // 继承Text类的draw方法绘制value

    // 在方框右侧外部绘制下标
    ctx.fillStyle = "red";
    const indexX = this.rx + SequentialStack.width + 10; // 右侧偏移5像素
    const indexY = this.ry + SequentialStack.height / 2; // 垂直居中
    fillText(ctx, this.index, indexX, indexY);
  }

  clear(ctx) {
    clearRect(ctx, this.rx - 1, this.ry - 1, SequentialStack.width + 15, SequentialStack.height + 1.4)
  }

  deepCopy() {
    return new SequentialStack(
      this.x,
      this.y,
      this.rx,
      this.ry,
      this.value,
      this.key,
      this.index,
      this.rColor
    );
  }

  // 静态方法：创建垂直排列的栈元素
  static createStack(nums, ctx) {
    const container = document.getElementById("canvas-container");

    let startX = container.clientWidth / 2

    let startY = container.clientHeight / 2

    let wrappers = [];
    for (let i = 0; i < nums.length; i++) {

      let tx = startX
      let ty = startY - SequentialStack.height * i;

      let x = tx + SequentialStack.width / 2
      let y = ty + SequentialStack.height / 2

      let stack = new SequentialStack(x, y, tx, ty, nums[i], i, i, 'white');

      stack.draw(ctx);

      wrappers.push(stack);
    }

    return wrappers;
  }

  static width = 60;  // 方框宽度
  static height = 30; // 方框高度
}

export class Wrapper3 extends Text {
  constructor(x, y, arcX, arcY, value, key, index, color) {
    super(x, y, value, key)
    this.arc = new Arc(arcX, arcY, index, color)
    this.drawLeftEnpoint = false
    this.drawRightEnpoint = false
    this.leftEnpoint = new Endpoint(this.x - Arc.R * Math.cos(Math.PI / 4), this.y + Arc.R * Math.sin(Math.PI / 4))
    this.rightEnpoint = new Endpoint(this.x + Arc.R * Math.cos(Math.PI / 4), this.y + Arc.R * Math.sin(Math.PI / 4))
    this.inDegree = 0
    this.initX = x
    this.initY = y
  }

  draw(ctx) {
    this.arc.draw(ctx)
    super.draw(ctx)
    this.drawEndpoint(ctx)

    if (this.arc.left) {
      let { fromX, fromY, toX, toY, color } = this.arc.left
      drawArrow(ctx, fromX, fromY, toX, toY, color)
    }

    if (this.arc.right) {
      let { fromX, fromY, toX, toY, color } = this.arc.right
      drawArrow(ctx, fromX, fromY, toX, toY, color)
    }

  }

  deepCopy() {
    let wrap = new Wrapper3(this.x, this.y, this.arc.x, this.arc.y, this.value, this.key, this.arc.index, this.arc.color)
    let arc = wrap.arc
    arc.setLeft(this.arc.left)
    arc.setRight(this.arc.right)
    return wrap
  }

  drawEndpoint(ctx) {

    if (this.drawLeftEnpoint === true) {
      this.leftEnpoint.draw(ctx)
    }

    if (this.drawRightEnpoint === true) {
      this.rightEnpoint.draw(ctx)
    }
  }

  setDrawEnpoint() {
    if (this.arc.left) {
      this.drawLeftEnpoint = false
    } else {
      this.drawLeftEnpoint = true
    }
    if (this.arc.right) {
      this.drawRightEnpoint = false
    } else {
      this.drawRightEnpoint = true
    }
  }

  setEndpoint(left, right) {
    this.drawLeftEnpoint = left
    this.drawRightEnpoint = right
  }

  static setDrawEndpointAll(wrappers, left, right) {
    wrappers.forEach(wrap => {
      wrap.setEndpoint(left, right)
    })
  }

  mouseIn(offsetX, offsetY) {
    let x0 = this.leftEnpoint.x
    let y0 = this.leftEnpoint.y
    if (((offsetX - x0) ** 2 + (offsetY - y0) ** 2) <= 7 * 7 && this.drawLeftEnpoint === true) {
      return 'left'
    }
    x0 = this.rightEnpoint.x
    y0 = this.rightEnpoint.y
    if (((offsetX - x0) ** 2 + (offsetY - y0) ** 2) <= 7 * 7 && this.drawRightEnpoint === true) {
      return 'right'
    }
  }

  setXY(x, y) {
    this.x = this.arc.x = x
    this.y = this.arc.y = y
    this.leftEnpoint.setXY(this.x - Arc.R * Math.cos(Math.PI / 4), this.y + Arc.R * Math.sin(Math.PI / 4))
    this.rightEnpoint.setXY(this.x + Arc.R * Math.cos(Math.PI / 4), this.y + Arc.R * Math.sin(Math.PI / 4))

    if (this.arc.left) {
      this.arc.left.fromX = this.leftEnpoint.x
      this.arc.left.fromY = this.leftEnpoint.y

      let { tox, toy } = this.getToXAndToY(x, y, this.left.arc.x, this.left.arc.y)
      this.arc.left.toX = tox
      this.arc.left.toY = toy
    }

    if (this.arc.right) {
      this.arc.right.fromX = this.rightEnpoint.x
      this.arc.right.fromY = this.rightEnpoint.y
      let { tox, toy } = this.getToXAndToY(x, y, this.right.arc.x, this.right.arc.y)
      this.arc.right.toX = tox
      this.arc.right.toY = toy
    }

    if (this.parent) {
      if (this.parent.left == this) {
        let x = this.parent.leftEnpoint.x
        let y = this.parent.leftEnpoint.y
        let { tox, toy } = this.parent.getToXAndToY(x, y, this.arc.x, this.arc.y)
        this.parent.arc.left.toX = tox
        this.parent.arc.left.toY = toy
      } else {
        let x = this.parent.rightEnpoint.x
        let y = this.parent.rightEnpoint.y
        let { tox, toy } = this.parent.getToXAndToY(x, y, this.arc.x, this.arc.y)
        this.parent.arc.right.toX = tox
        this.parent.arc.right.toY = toy
      }
    }
  }

  getToXAndToY(x, y, x1, y1) {
    let k = (y - y1) / (x - x1)
    let rad = Math.atan(k)
    if (x1 < x) {
      let tox = x1 + Arc.R * Math.cos(rad)
      let toy = y1 + Arc.R * Math.sin(rad)
      return { tox, toy }
    } else {
      let tox = x1 + Arc.R * Math.cos(rad + Math.PI)
      let toy = y1 + Arc.R * Math.sin(rad + Math.PI)
      return { tox, toy }
    }
  }

  line(target, isLeft) {
    if (target.inDegree === 1) return

    let cur = this
    while (cur) {
      if (cur === target) return
      cur = cur.parent
    }

    if (isLeft) {
      let x = this.leftEnpoint.x
      let y = this.leftEnpoint.y
      let { tox, toy } = this.getToXAndToY(x, y, target.arc.x, target.arc.y)
      let line = new Line(x, y, tox, toy)
      this.arc.setLeft(line)
      this.left = target
    } else {
      let x = this.rightEnpoint.x
      let y = this.rightEnpoint.y
      let { tox, toy } = this.getToXAndToY(x, y, target.arc.x, target.arc.y)
      let line = new Line(x, y, tox, toy)
      this.arc.setRight(line)
      this.right = target
    }

    target.parent = this
    target.inDegree = 1
  }

  drawLine(ctx, endpoint, toX, toY) {
    ctx.beginPath()
    moveTo(ctx, endpoint.x, endpoint.y)
    lineTo(ctx, toX, toY)
    ctx.stroke()
    drawArrow(ctx, endpoint.x, endpoint.y, toX, toY)
  }


  oncontextmenu(e, source, target, comp, isDeleteNode) {
    let container = document.getElementById('canvas-container')
    e.preventDefault();
    let menu = document.getElementById('menu')
    if (menu) container.removeChild(menu)
    menu = document.createElement('div')
    menu.setAttribute('id', 'menu');
    menu.innerText = '删除'
    menu.style.background = '#E45253'
    menu.style.padding = '5px 20px'
    menu.style.fontSize = '13px'
    menu.style.position = 'absolute'
    menu.style.left = e.pageX + 10 + 'px'
    menu.style.top = e.pageY + 10 + 'px'
    menu.style.color = 'white'
    menu.style.borderRadius = '5px'
    menu.style.cursor = 'pointer'
    container.appendChild(menu)

    menu.addEventListener('click', () => {
      container.removeChild(menu)
      if (isDeleteNode) {
        if (source === comp.root) {
          comp.root = undefined
        }
        if (source.parent) {
          if (source.parent.left === source) {
            source.parent.left = undefined
            source.parent.arc.left = undefined
          } else {
            source.parent.right = undefined
            source.parent.arc.right = undefined
          }
        }
        if (source.left) {
          source.left.inDegree = 0
          source.left.parent = undefined
          source.left = undefined
          source.arc.left = undefined
        }
        if (source.right) {
          source.right.inDegree = 0
          source.right.parent = undefined
          source.right = undefined
          source.arc.right = undefined
        }
        source.parent = undefined
        comp.wrappers = comp.wrappers.filter(wrap => wrap !== source)
      } else {
        if (source.left === target) {
          source.left = undefined
          source.arc.left = undefined
        } else {
          source.right = undefined
          source.arc.right = undefined
        }
        target.parent = undefined
        target.inDegree = 0

      }
      comp.draw(comp.ctx, comp.wrappers)
    })
  }


  isInLine(offsetX, offsetY, Comp) {
    let container = document.getElementById('canvas-container')
    if (this.arc.left) {
      let { fromX, fromY, toX, toY } = this.arc.left
      let k = (fromY - toY) / (fromX - toX)
      let b = fromY - k * fromX
      // y = kx + b
      let y = k * offsetX + b
      let maxX = toX > fromX ? toX : fromX
      let minX = toX > fromX ? fromX : toX
      if (Math.abs(y - offsetY) < 10 && offsetX > minX && offsetX < maxX) {
        this.arc.left.color = 'red'
        this.arc.left.lineWidth = 3 * window.devicePixelRatio
        document.body.style.cursor = 'pointer'
        container.oncontextmenu = e => this.oncontextmenu(e, this, this.left, Comp, false)
        return true
      } else {
        this.arc.left.color = 'black'
        this.arc.left.lineWidth = 2 * window.devicePixelRatio
      }
    }

    if (this.arc.right) {
      let { fromX, fromY, toX, toY } = this.arc.right
      let k = (fromY - toY) / (fromX - toX)
      let b = fromY - k * fromX
      // y = kx + b
      let y = k * offsetX + b
      let maxX = toX > fromX ? toX : fromX
      let minX = toX > fromX ? fromX : toX
      if (Math.abs(y - offsetY) < 10 && offsetX > minX && offsetX < maxX) {
        this.arc.right.color = 'red'
        this.arc.right.lineWidth = 3 * window.devicePixelRatio
        document.body.style.cursor = 'pointer'
        container.oncontextmenu = e => this.oncontextmenu(e, this, this.right, Comp, false)
        return true
      } else {
        this.arc.right.color = 'black'
        this.arc.right.lineWidth = 2 * window.devicePixelRatio
      }
    }
    document.body.style.cursor = 'default'

    container.oncontextmenu = e => {
      e.preventDefault()
      Comp.dragWrap = undefined;
      Comp.state = State.INDE;
      let menu = document.getElementById('menu')
      if (menu) container.removeChild(menu)
    };
    return false
  }

  deleteNode(Comp) {
    let container = document.getElementById('canvas-container')
    container.oncontextmenu = e => this.oncontextmenu(e, this, undefined, Comp, true)
  }
}
